/*
 * cubeIO.c
 *
 *  Created on: Sep 28, 2013
 *  @author Daniel Rogers
 */

#include "cubeIO.h"

bool loadCubeFromFile(const char * fname, Cube * state, uint8_t * htables[3])
{

    struct stat sz;
    bool success = true;

    FILE * cube_file = fopen(fname, "r");
    if (!cube_file)
    {
        //could not open cube file
        puts("Unable to open cube file for reading");
        return false;
    }

    stat(fname, &sz);
    //prevent overflow
    char * buf;
    buf = malloc(sizeof(char) * sz.st_size);

    //stores characters for cube state
    char sides[55];
    sides[54] = '\0';
    char counts[6] = {0, 0, 0, 0, 0, 0};

    uint8_t c = 0;

    fread(buf, sizeof(char), sz.st_size, cube_file);
    fclose(cube_file);

    //Loop to end of file, and don't exceed array size. Check to make sure no
    //  invalid character was encountered
    for (int i = 0, fi = 0; (i < 54) && (fi < sz.st_size) && (success); ++fi)
    {
        //only care about {R, O, Y, G, B, W}
        switch (buf[fi])
        {
        //Check for sides
        case 'R':
            c = 1;
            break;
        case 'O':
            c = 2;
            break;
        case 'Y':
            c = 3;
            break;
        case 'G':
            c = 4;
            break;
        case 'B':
            c = 5;
            break;
        case 'W':
            c = 6;
            break;

            //Check for whitespace
        case ' ':
        case '\t':
        case '\n':
        case '\r':
            //Set c to zero to simplify check for face seen
            c = 0;
            break;

            //Invalid character
        default:
            printf("Invalid character: %d, @%d\n", buf[fi], fi);
            success = false;
            c = 0;
        }    //End character check switch

        //If we loaded a face
        if (c)
        {
            //Store the item
            sides[i++] = buf[fi];
            //Keep track of how many faces we saw
            ++counts[c - 1];
        }
    }
    free(buf);

    if (!success)
    {
        //Saw an invalid character
        return false;
    }

#ifdef DEBUG
    printf("%s\n", sides);
#endif

    //Test proper count of faces
    for (uint8_t i = 1; i < 6; ++i)
    {
        if (counts[i] != counts[i - 1])
        {
            printf("Improper number of faces: %d, %d @%d\n", counts[i], counts[i-1], i);
            return false;
        }
    }

    //Test for center cubies being proper
    //  centers at 4, 19, 22, 25, 40, 49
    //             R   G   Y   B   O   W
    if (sides[4] != 'R' || sides[19] != 'G' || sides[22] != 'Y'
            || sides[25] != 'B' || sides[40] != 'O' || sides[49] != 'W')
    {
        puts("Bad center faces");
        return false;
    }

    //Test for good corner values & build corner values
    //Order: [top/bot, front/back, left/right], ccw from top, back, right
    //  Corner faces at: [47, 44, 35], [45, 42, 27], [51, 0, 9], [53, 2, 11],
    //                   [32, 38, 33], [30, 36, 29], [12, 6, 11], [14, 8, 15]
    uint8_t corners[8];
    char cfaces[3];
    uint8_t cparity[8];

    //This is really ugly, sorry
    //Top corners [47, 44, 35], [45, 42, 27], [51, 0, 9], [53, 2, 17]
    cfaces[0] = sides[47];
    cfaces[1] = sides[44];
    cfaces[2] = sides[35];
    success &= evaluateCorner(cfaces, corners, cparity, &state->corner_spin, 0);

    cfaces[0] = sides[45];
    cfaces[1] = sides[42];
    cfaces[2] = sides[27];
    success &= evaluateCorner(cfaces, corners, cparity, &state->corner_spin, 1);

    cfaces[0] = sides[51];
    cfaces[1] = sides[0];
    cfaces[2] = sides[9];
    success &= evaluateCorner(cfaces, corners, cparity, &state->corner_spin, 2);

    cfaces[0] = sides[53];
    cfaces[1] = sides[2];
    cfaces[2] = sides[17];
    success &= evaluateCorner(cfaces, corners, cparity, &state->corner_spin, 3);

    //Bottom corners [32, 38, 33], [30, 36, 29], [12, 6, 11], [14, 8, 15]
    cfaces[0] = sides[32];
    cfaces[1] = sides[38];
    cfaces[2] = sides[33];
    success &= evaluateCorner(cfaces, corners, cparity, &state->corner_spin, 4);

    cfaces[0] = sides[30];
    cfaces[1] = sides[36];
    cfaces[2] = sides[29];
    success &= evaluateCorner(cfaces, corners, cparity, &state->corner_spin, 5);

    cfaces[0] = sides[12];
    cfaces[1] = sides[6];
    cfaces[2] = sides[11];
    success &= evaluateCorner(cfaces, corners, cparity, &state->corner_spin, 6);

    cfaces[0] = sides[14];
    cfaces[1] = sides[8];
    cfaces[2] = sides[15];
    success &= evaluateCorner(cfaces, corners, cparity, &state->corner_spin, 7);

    if (!success)
    {
        //Something was wrong with the corner face combinations
        return false;
    }

    //Store corners in the cube state
    encodeCorners(corners, &state->corners);

    //Test for good edge values & build edge values
    //  ccw from top back, [top/bot, other] or [front/back, other]
    //  top: [46, 43], [48, 18], [52,  1], [50, 26]
    //  mid: [41, 34], [39, 28], [ 3, 10], [ 5, 16]
    //  bot: [31, 37], [21, 20], [13,  7], [23, 24]
    uint8_t edges[12];
    char efaces[2];
    uint8_t eparity[12];

    //More ugly code
    //Top edge pieces [46, 43], [48, 18], [52,  1], [50, 26]
    efaces[0] = sides[46];
    efaces[1] = sides[43];
    success &= evaluateEdge(efaces, edges, eparity, &state->edge_spin, 0);

    efaces[0] = sides[48];
    efaces[1] = sides[18];
    success &= evaluateEdge(efaces, edges, eparity, &state->edge_spin, 1);

    efaces[0] = sides[52];
    efaces[1] = sides[1];
    success &= evaluateEdge(efaces, edges, eparity, &state->edge_spin, 2);

    efaces[0] = sides[50];
    efaces[1] = sides[26];
    success &= evaluateEdge(efaces, edges, eparity, &state->edge_spin, 3);

    //Middle edge pieces [41, 34], [39, 28], [ 3, 10], [ 5, 16]
    efaces[0] = sides[41];
    efaces[1] = sides[34];
    success &= evaluateEdge(efaces, edges, eparity, &state->edge_spin, 4);

    efaces[0] = sides[39];
    efaces[1] = sides[28];
    success &= evaluateEdge(efaces, edges, eparity, &state->edge_spin, 5);

    efaces[0] = sides[3];
    efaces[1] = sides[10];
    success &= evaluateEdge(efaces, edges, eparity, &state->edge_spin, 6);

    efaces[0] = sides[5];
    efaces[1] = sides[16];
    success &= evaluateEdge(efaces, edges, eparity, &state->edge_spin, 7);

    //Bottom edge pieces [31, 37], [21, 20], [13,  7], [23, 24]
    efaces[0] = sides[31];
    efaces[1] = sides[37];
    success &= evaluateEdge(efaces, edges, eparity, &state->edge_spin, 8);

    efaces[0] = sides[21];
    efaces[1] = sides[20];
    success &= evaluateEdge(efaces, edges, eparity, &state->edge_spin, 9);

    efaces[0] = sides[13];
    efaces[1] = sides[7];
    success &= evaluateEdge(efaces, edges, eparity, &state->edge_spin, 10);

    efaces[0] = sides[23];
    efaces[1] = sides[24];
    success &= evaluateEdge(efaces, edges, eparity, &state->edge_spin, 11);

    if (!success)
    {
        //Something was wrong with the edge face combinations
        return false;
    }

    //Store edges in the cube state
    compressEdges(edges, state->edges);

    //Parity check
    //For reference:
    //http://math.stackexchange.com/questions/127577/how-to-tell-if-a-rubiks-cube-is-solvable
    uint8_t parity = 0;
    uint8_t distances[3];

    //Permutation parity
    //  Get the table heuristics for moves to solve, this will be min number of
    //      turns to reach the position
    //  Add them and mod 2
    //      if it is 0 then it's good
    //get lookup indexes
    getCubeHeuristics(htables, *state, distances);
    parity += distances[0];
    parity += distances[1];
    parity += distances[2];

    parity %= 2;

    if (parity)
    {
        puts("Bad permutation parity");
        return false;
    }

    //Corner Parity
    for (uint8_t i = 0; i < 8; ++i)
    {
        parity += cparity[i];
    }

    //Must be mult 3
    parity %= 3;

    if (parity)
    {
        puts("Bad corner parity");
        printf("%d\n", parity);
        return false;
    }

    //Edge parity
    for (uint8_t i = 0; i < 12; ++i)
    {
        parity += eparity[i];
    }

    //Must be even
    parity %= 2;

    if (parity)
    {
        puts("Bad edge parity");
        return false;
    }

    //Everything is a-okay
    return true;
}

bool loadHTables(uint8_t * htables[3])
{
    bool success = true;

    FILE * cfile;
    FILE * efile1;
    FILE * efile2;

    cfile = fopen("corner_table.bin", "rb");
    efile1 = fopen("edge_table1.bin", "rb");
    efile2 = fopen("edge_table2.bin", "rb");

    if (!cfile || !efile1 || !efile2)
    {
        puts("Unable to open a table file for reading");
        success = false;
    }
    else
    {
        /*
         * Allocate memory to store tables
         */
        htables[0] = malloc(CORNER_SLOTS * sizeof(uint8_t));
        htables[1] = malloc(EDGE_SLOTS * sizeof(uint8_t));
        htables[2] = malloc(EDGE_SLOTS * sizeof(uint8_t));

        /*
         * Read in files
         */
        fread(htables[0], sizeof(uint8_t), CORNER_SLOTS, cfile);
        fread(htables[1], sizeof(uint8_t), EDGE_SLOTS, efile1);
        fread(htables[2], sizeof(uint8_t), EDGE_SLOTS, efile2);

        fclose(cfile);
        fclose(efile1);
        fclose(efile2);
    }

    return success;
}

void freeHTables(uint8_t * htables[3])
{
    //Dump the memory
    int i;
    for (i = 0; i < 3; ++i)
    {
        free(htables[i]);
        htables[i] = NULL;
    }
}
